
/* tape input/output routines */

#include <limits.h>
#include <ctype.h>
#include <stdlib.h>
#include "tar.h"
#include "dir.h"
#include "disc.h"
#include "timecon.h"
#include "perms.h"
#include "rmt.h"
#include "tapeio.h"


static long VolFreeSpace;


static int FreeSpace(char *FileName, int InitLen, int InitInc, int WriteMode) {
  int FileHandle,reallen,len,inc;
  os_error *Error;
  os_regset Regs;

  do {
    do {
      Regs.r[0] = WriteMode ? 0x83 : 0xC3;
      Regs.r[1] = (int)FileName;
    } while (!chkos(os_find(&Regs)));
    FileHandle = Regs.r[0];
    Regs.r[0] = 6;
    Regs.r[1] = FileHandle;
    len = InitLen;
    reallen = InitLen;
    inc = InitInc;
    do {
      Regs.r[2] = len + inc;
      Error = os_args(&Regs);
      if (Error == NULL) {
        fprintf(stderr,"\rFree Space = %d",reallen);
        reallen = Regs.r[2];
        len = len + inc;
        if (reallen < len)
          inc = 0;
      } else {
        inc = 0;
      }
    } while (inc > 0);
    do {
      Regs.r[0] = 0;
      Regs.r[1] = FileHandle;
    } while (!chkos(os_find(&Regs)));
    InitInc /= 4;
    InitLen = reallen;
  } while (InitInc >= 1024);
  if (WriteMode) {
    do {
      Regs.r[0] = 11;
      Regs.r[1] = (int)FileName;
      Regs.r[2] = tarFileType;
      Regs.r[4] = 0;
      Regs.r[5] = reallen;
    } while (!chkos(os_swix(OS_File,&Regs)));
  }
  fprintf(stderr,"\rFree Space = %d\n",reallen);
  return reallen;
} /* FreeSpace */


static void WriteDiscHeader(int DiscNo) {
  Block_t vblock;

  memset(vblock.Block, 0, RECORDSIZE);
  vblock.Header.linkflag = LF_VOLHDR;
  sprintf(vblock.Header.name,"Volume #%d",DiscNo);
  sprintf(vblock.Header.chksum, "%6o", checksum(&vblock));
  writetape(vblock.Block);
} /* WriteDiscHeader */


static void OpenArchiveFile(int WriteMode) {
  char *mode;
  char answer;

  if (!WriteMode) {
    mode = "rb";
  } else {
    if (CreateArchive)
      mode = "wb";
    else
      mode = "rb+";
  }
  while ((ArchiveFD = fopen(ArchiveName, mode)) == NULL) {
    fprintf(stderr, "tar: cannot open %s\n", ArchiveName);
    if (!QuietExecution) {
      fprintf(stderr, "tar: retry? ");
      answer = Decision('y');
    }
    if (QuietExecution || answer == 'n' || answer == 'N') {
      Terminate(8);
    }
  }
  ArchiveOpen = 1;
} /* OpenArchiveFile */


#ifndef __riscos
static void OpenArchiveRMT(int WriteMode) {
  int mode;
  char answer;

  if (!WriteMode) {
    mode = O_RDONLY;
  } else {
    if (CreateArchive)
      mode = O_CREAT | O_WRONLY;
    else
      mode = O_APPEND | O_RDWR;
  }
  while ((rmt_fd = rmtopen(ArchiveName,mode,0666)) == NULL) {
    fprintf(stderr, "tar: cannot open %s\n", ArchiveName);
    if (!QuietExecution) {
      fprintf(stderr, "tar: retry? ");
      answer = Decision('y');
    }
    if (QuietExecution || answer == 'n' || answer == 'N') {
      Terminate(8);
    }
  }
  ArchiveOpen = 1;
} /* OpenArchiveRMT */
#endif

static void NewTape(int WriteMode) {
  recno =0;
  first = 0;
  switch (tapedevice) {
    case tapedevice_DISC:
      InitDisc();
      break;
    case tapedevice_FILE:
      VolFreeSpace = INT_MAX;
      if (MultipleVolumes) {
        if (AppendToArchive)
          VolFreeSpace = FreeSpace(ArchiveName,0,512000,WriteMode);
        else
          VolFreeSpace = INT_MAX;
      }
      OpenArchiveFile(WriteMode);
      break;
#ifndef __riscos
    case tapedevice_RMT:
      VolFreeSpace = INT_MAX;
      OpenArchiveRMT(WriteMode);
      break;
#endif
  }
} /* NewTape */


static void WriteDiscEnd(int DiscNo) {
  Block_t vblock;

  memset(vblock.Block, 0, RECORDSIZE);
  vblock.Header.linkflag = LF_VOLEND;
  sprintf(vblock.Header.name,"Volume #%d",DiscNo);
  sprintf(vblock.Header.chksum, "%6o", checksum(&vblock));
  writetape(vblock.Block);
} /* WriteDiscEnd */


static int chkchecksum(Block_t *Block) {
  int SavedCheckSum;
  int CalculatedCheckSum;

  sscanf(Block->Header.chksum, "%o", &SavedCheckSum);
  CalculatedCheckSum = checksum(Block);
  if (SavedCheckSum != CalculatedCheckSum) {
    fprintf(stderr, "tar: directory checksum error (%d != %d)\n",
      SavedCheckSum, CalculatedCheckSum);
    if (IgnoreArchiveErrors) {
      return 0;
    }
    Terminate(9);
  }
  return(1);
} /* chkchecksum */


static void ReadDiscHeader(int ExpectedDiscNo) {
  int ActualDiscNo=0;
  int ok;
  Block_t vblock;

  do {
    ok = 1;
    if (ExpectedDiscNo > 1) {
      fprintf(stderr,"Please insert disc #%d:",ExpectedDiscNo);
      while (getchar() != '\n')
        ;
    }
    NewTape(0);
    readtape(vblock.Block);
    chkchecksum(&vblock);
    if (vblock.Header.linkflag != LF_VOLHDR) {
      fprintf(stderr,"tar: disc header expected\n");
      ok = 0;
    } else {
      sscanf(vblock.Header.name,"Volume #%d",&ActualDiscNo);
      if (ActualDiscNo != ExpectedDiscNo) {
        fprintf(stderr,"tar: discs out of sequence (disc #%d)\n",ActualDiscNo);
        if (ExpectedDiscNo > 1)
          ok = 0;
        else {
          DiscNo = ActualDiscNo;
          ok = 1;
        }
      }
    }
  } while (!ok);
} /* ReadDiscHeader */


static void ChkDiscEnd(Block_t *vblock, int ExpectedDiscNo) {
  int DiscNo=0;

  if (vblock->Header.linkflag != LF_VOLEND) {
    fprintf(stderr,"tar: disc end expected\n");
    return;
  }
  sscanf(vblock->Header.name,"Volume #%d",&DiscNo);
  if (DiscNo != ExpectedDiscNo)
    fprintf(stderr,"tar: disc end out of sequence\n");
  if (MultipleVolumes && tapedevice == tapedevice_FILE) {
    fclose(ArchiveFD);
    ArchiveOpen = 0;
  }
} /* ChkDiscEnd */


static void NextDisc() {
  ChkDiscEnd(&Block,DiscNo++);
  ReadDiscHeader(DiscNo);
} /* NextDisc */


void NewDisc() {
  WriteDiscEnd(DiscNo++);
  flushtape();
  os_cli("dismount 0");
  fprintf(stderr,"Please insert disc #%d:",DiscNo);
  while (getchar() != '\n')
    ;
  if (FormatFloppies && MultipleVolumes && CreateArchive) {
    FormatDisc();
  }
  NewTape(1);
  WriteDiscHeader(DiscNo);
} /* NewDisc */


void open_writetape(void) {
  char answer;

  switch (tapedevice) {
    case tapedevice_DISC:
      SetupDisc(driveno,format);
      if (!NoDiskDestroyConfirmation && !QuietExecution) {
        fprintf(stderr,
          "tar: warning:\a data on drive %d will be overwritten!\n", driveno);
        fprintf(stderr, "tar: continue? ");
        answer = Decision('n');
        if (answer == 'n' || answer == 'N') {
          Terminate(10);
        }
      }
      if (FormatFloppies && MultipleVolumes && CreateArchive) {
        FormatDisc();
      }
      if (MultipleVolumes && CreateArchive) {
        WriteDiscHeader(DiscNo);
      }
      break;
    case tapedevice_FILE:
      if (FormatFloppies && MultipleVolumes && CreateArchive) {
        FormatDisc();
      }
      if (MultipleVolumes)
        VolFreeSpace = FreeSpace(ArchiveName,0,512000,1);
      else
        VolFreeSpace = INT_MAX;
      OpenArchiveFile(1);
      if (MultipleVolumes && CreateArchive) {
        WriteDiscHeader(DiscNo);
      }
      break;
#ifndef __riscos
    case tapedevice_RMT:
      VolFreeSpace = INT_MAX;
      OpenArchiveRMT(1);
      if (MultipleVolumes && CreateArchive) {
        WriteDiscHeader(DiscNo);
      }
      break;
#endif
  }
} /* open_writetape */


void open_readtape(void) {
  switch (tapedevice) {
    case tapedevice_DISC:
      SetupDisc(driveno,format);
      break;
    case tapedevice_FILE:
      VolFreeSpace = INT_MAX;
      OpenArchiveFile(0);
      break;
#ifndef __riscos
    case tapedevice_RMT:
      VolFreeSpace = INT_MAX;
      OpenArchiveRMT(0);
      break;
#endif
  }
} /* open_readtape */


int checksum(Block_t *Block) {
  int i;
  char *cp;

  for (cp = Block->Header.chksum; cp < &Block->Header.chksum[sizeof(Block->Header.chksum)]; cp++)
    *cp = ' ';
  i = 0;
  for (cp = Block->Block; cp < &Block->Block[RECORDSIZE]; cp++)
    i += *cp;
  return (i);
} /* checksum */


static int WriteBufferBlocks(char *Buffer, int NumBlocks) {
  int Written;

  switch (tapedevice) {
    case tapedevice_DISC:
      return WriteDisc(Buffer, (long)NumBlocks * RECORDSIZE) / RECORDSIZE;
    case tapedevice_FILE:
      Written = fwrite(Buffer, RECORDSIZE, NumBlocks, ArchiveFD);
      if (VolFreeSpace != INT_MAX) {
        VolFreeSpace -= (long)Written * (long)RECORDSIZE;
      }
      return Written;
#ifndef __riscos
    case tapedevice_RMT:
      Written = rmtwrite(rmt_fd, Buffer, NumBlocks * RECORDSIZE);
      if (VolFreeSpace != INT_MAX) {
        VolFreeSpace -= (long)Written;
      }
      return Written;
#endif
  }
  return 0;
} /* WriteBufferBlocks */


static int ReadBufferBytes(char *Buffer, int NumBytes) {
  int NumRead;

  switch (tapedevice) {
    case tapedevice_DISC:
      return(ReadDisc(Buffer,(long)NumBytes));
    case tapedevice_FILE:
      NumRead = fread(Buffer, 1, NumBytes, ArchiveFD);
      if (VolFreeSpace != INT_MAX) {
        VolFreeSpace -= NumRead;
      }
      return NumRead;
#ifndef __riscos
    case tapedevice_RMT:
      NumRead = rmtread(rmt_fd, Buffer, NumBytes);
      if (VolFreeSpace != INT_MAX) {
        VolFreeSpace -= NumRead;
      }
      return NumRead;
#endif
  }
  return 0;
} /* ReadBufferBytes */


void flushtape(void) {
  if (recno > 0 && recno < nblock) {
    WriteBufferBlocks((char *)TmpBlock,recno);
  }
  switch (tapedevice) {
    case tapedevice_DISC:
      FlushDisc();
      break;
    case tapedevice_FILE:
      fclose(ArchiveFD);
      SetFileType(ArchiveName, tarFileType);
      ArchiveOpen = 0;
      break;
#ifndef __riscos
    case tapedevice_RMT:
      rmtclose(rmt_fd);
      ArchiveOpen = 0;
      break;
#endif
  }
} /* flushtape */


static void writeblocks() {
  if (WriteBufferBlocks((char *)TmpBlock, nblock) < nblock) {
    fprintf(stderr, "tar: tape write error\n");
    Terminate(11);
  }
  recno = 0;
} /* writeblocks */


int writetape(char *buffer) {
  first = 1;
  if (recno >= nblock)
    writeblocks();
  memcpy((char *)&TmpBlock[recno++], buffer, RECORDSIZE);
  if (recno >= nblock)
    writeblocks();
  return(RECORDSIZE);
} /* writetape */


void putempty(void) {
  char buf[RECORDSIZE];

  memset(buf, 0, sizeof (buf));
  writetape(buf);
} /* putempty */


int endtape(void) {
  if (IgnoreArchiveErrors) {
    return 0;
  }
  return (Block.Header.name[0] == '\0');
} /* endtape */


static int bread(char *buf, int size) {
  int count;
  static int lastread = 0;

  if (!Reblock)
    return (ReadBufferBytes(buf, size));

  for (count = 0; count < size; count += lastread) {
    if (lastread < 0) {
      if (count > 0)
        return (count);
      return (lastread);
    }
    lastread = ReadBufferBytes(buf, size - count);
    buf += lastread;
  }
  return (count);
} /* bread */


int readtape(char *buffer) {
  int NumBytesRead;

  if (recno >= nblock || first == 0) {
    if ((NumBytesRead = bread((char *)TmpBlock, RECORDSIZE*nblock)) <= 0) {
      fprintf(stderr, "tar: tape read error\n");
      Terminate(12);
    }
    if ((NumBytesRead % RECORDSIZE) != 0) {
      fprintf(stderr, "tar: tape blocksize error\n");
      if (first == 0)
        Terminate(13);
    }
    NumBlocksRead = NumBytesRead/RECORDSIZE;
    if (first == 0) {
      if (NumBlocksRead != nblock) {
        fprintf(stderr, "tar: blocksize = %d\n", NumBlocksRead);
        nblock = NumBlocksRead;
      }
    }
    recno = 0;
  }
  first = 1;
  memcpy(buffer, (char *)&TmpBlock[recno++], RECORDSIZE);
  return (RECORDSIZE);
} /* readtape */


void backtape(void) {
  recno--;
  switch (tapedevice) {
    case tapedevice_DISC:
      BackDisc();
      break;
    case tapedevice_FILE:
      fseek(ArchiveFD, -(long)RECORDSIZE * NumBlocksRead, SEEK_CUR);
      break;
#ifndef __riscos
    case tapedevice_RMT:
      rmtlseek(rmt_fd,-(long)RECORDSIZE * NumBlocksRead, SEEK_CUR);
      break;
#endif
  }
} /* backtape */


void passtape(long Length) {
  long blocks;
  char buf[RECORDSIZE];

  if (Block.Header.linkflag == '1')
    return;
  blocks = Length;
  blocks += RECORDSIZE-1;
  blocks /= RECORDSIZE;
  while (!UserInterrupt && blocks-- > 0)
    readtape(buf);
} /* passtape */


void getdir(CatInfo *InfoBlk) {
  int mode, NameLen, FileType;
  int ThisDiscNo;
  long mtime;

top:
  readtape((char *)&Block);
  if (Block.Header.name[0] == 0) {
    if (IgnoreArchiveErrors) {
      goto top;
    } else {
      return;
    }
  }

  if (!chkchecksum(&Block))
    goto top;

  if (Block.Header.linkflag == LF_VOLEND) {
    if (MultipleVolumes) {
      NextDisc();
    } else {
      fprintf(stderr,"tar: unexpected end of disc\n");
      Terminate(14);
    }
    return;
  }

  if (Block.Header.linkflag == LF_VOLHDR) {
    sscanf(Block.Header.name,"Volume #%d",&ThisDiscNo);
    if (ThisDiscNo != 1) {
      fprintf(stderr,"tar: unexpected disc header (disc #%d)\n",ThisDiscNo);
      DiscNo = ThisDiscNo;
      return;
    }
    MultipleVolumes = 1;
    return;
  }

  memset((char *)InfoBlk,0,sizeof(CatInfo));
  sscanf(Block.Header.size, "%lo", &InfoBlk->Length);
  if (isArchie = (strcmp(Block.Header.magic, ARC_MAGIC) == 0), isArchie) {
    sscanf(Block.Header.LoadAddress, "%lo", &InfoBlk->LoadAddress);
    sscanf(Block.Header.ExecAddress, "%lo", &InfoBlk->ExecAddress);
    sscanf(Block.Header.Attr, "%lo", &InfoBlk->Attr);
    sscanf(Block.Header.Date,"%x %lx",&InfoBlk->DateAndName[4],&InfoBlk->DateAndName);
  } else {
    sscanf(Block.Header.mode,"%o",&mode);
    InfoBlk->Attr = unix_to_fs_perms(mode);
    if (CommaFileTypes && (NameLen = strlen(Block.Header.name)) > 4 &&
        Block.Header.name[NameLen - 4] == ',' &&
      isxdigit(Block.Header.name[NameLen - 3]) &&
      isxdigit(Block.Header.name[NameLen - 2]) &&
      isxdigit(Block.Header.name[NameLen - 1])) {
      FileType = (int)strtoul(Block.Header.name + NameLen - 3, NULL, 16);
      Block.Header.name[NameLen - 4] = 0;
    } else {
      FileType = (mode & (XOWN | XOTH | XGRP)) ? ft_Absolute : ft_Text;
    }
    InfoBlk->LoadAddress = 0xFFF00000L | ((long)FileType << 8);
    sscanf(Block.Header.mtime,"%lo",&mtime);
    unix_to_fs_time((unsigned char *)&InfoBlk->DateAndName,mtime);
    InfoBlk->ExecAddress = *(long *)&InfoBlk->DateAndName;
    InfoBlk->LoadAddress |= InfoBlk->DateAndName[4];
  }
  if (Block.Header.compressed == CF_COMPRESSED)
    sscanf(Block.Header.compression,"%o",&compression);
} /* getdir */


long FreeOnTape(void) {
  return (tapedevice == tapedevice_DISC ? FreeOnDisc() : VolFreeSpace) -
         (long)recno * (long)RECORDSIZE;
} /* FreeOnTape */
